/*  This file is a part of JJSched source code
    JJSched - Java Jabber Scheduler - a simple Jabber scheduling application
    Copyright (C) 2008 Cestmir "CZestmyr" Houska

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/> */

package czestmyr.jjsched;

import java.lang.*;
import java.util.*;
import java.io.*;

/**
 * Processes the configuration options specified in the configuration file.
 * Also stores these options within itself and stores global macros in the
 * provided instance of Macros class
 */
public class Config{
    /**
     * Constructor. This class needs the macro class reference to be able to store the
     * macro definitions in it.
     *
     * @param   macros  Instance of the class Macros that will be modified 
     */
    Config(Macros macros){
        theMacros = macros;
    }

    /**
     * Parses the given configuration file. Read the example configuration file in
     * the JJSched directory or in user documentation to learn more about configuration
     * file of JJSched
     *
     * @param   configFilename  The path to the configuration file with configuration for
     *                          JJSched
     * @throws  Exception       If the file cannot be accesses or if it does not exist.
     *                          An exception is also thrown in case of incorrect format
     *                          of the configuration file
     */
    public void parse(String configFilename) throws Exception{
        BufferedReader bRead = new BufferedReader(new FileReader(configFilename));

        String line;
        while ((line = bRead.readLine()) != null){
            LinkedList<String> lineTokens = StringParser.parseLine(line);

            if (lineTokens.isEmpty()) continue;

            States s = States.INITIAL;
            String keyword = null;
            Vector<String> body;

            Iterator it = lineTokens.iterator();
            int i = 0;
            while (it.hasNext()){
                String currentToken = (String)it.next();
                switch(s){
                    case INITIAL:
                        if (currentToken.compareTo("macro") == 0)
                            s = States.MACRO;
                        else {
                            if (currentToken.compareTo("=") == 0)
                                throw new Exception("Keyword or option name expected as first token: "+line);
                            keyword = currentToken;
                            s = States.OPERATOR;
                        }
                    break;
                    case MACRO:
                        if (currentToken.compareTo("=") == 0)
                            throw new Exception("Macro keyword expected: "+line);
                        else {
                            keyword = currentToken;
                            s = States.OPERATORMACRO;
                        }
                    break;
                    case OPERATOR:
                        if (currentToken.compareTo("=") != 0)
                            throw new Exception("Operator '=' expected after option name: "+line);
                        else s = States.VALUE;
                    break;
                    case OPERATORMACRO:
                        if (currentToken.compareTo("=") != 0)
                            throw new Exception("Operator '=' expected after macro keyword: "+line);
                        else s = States.BODY;
                    break;
                    case VALUE:
                        if (currentToken.compareTo("int") == 0)
                            s = States.INTVALUE;
                        else {
                            options.put(keyword, currentToken);
                            s = States.END;
                        }
                    break;
                    case BODY:
                        theMacros.newMacro(keyword, lineTokens.subList(i, lineTokens.size()).iterator());
                        while (it.hasNext()) it.next();
                        s = States.END;
                    break;
                    case INTVALUE:
                        options.put(keyword, new Integer(currentToken));
                        s = States.END;
                    break;
                    case END:
                        throw new Exception("Further tokens after legal construct: "+line);
                }
                ++i;
            }

            if (s != States.END && s != States.INITIAL)
                throw new Exception("Incomplete option definition: "+line);
        }
    }

    /**
     * Returns the value of the given configuration option as String.
     * Configuration options can be either strings or integers, so you cannot use this
     * method for the same keyword along with the getInt(String keyword) method.
     *
     * @param   keyword The keyword of the sought option
     * @return          The String value of the given option
     */
    public String getString(String keyword){
        if (options.get(keyword).getClass().getName() == "java.lang.Integer"){
            return null;
        }
        return (String)options.get(keyword);
    }

    /**
     * Returns the value of the given configuration option as Integer.
     * Configuration options can be either strings or integers, so you cannot use this
     * method for the same keyword along with the getString(String keyword) method.
     *
     * @param   keyword The keyword of the sought option
     * @return          The value of the given option as an Integer
     */
    public Integer getInt(String keyword){
        if (options.get(keyword).getClass().getName() == "java.lang.String"){
            return null;
        }
        return (Integer)options.get(keyword);
    }

    /** Reference to the instance of the class that holds macro definitions */
    private Macros theMacros;

    /** Hash map for storing the option values */
    private HashMap<String, Object> options = new HashMap<String, Object>();
}

/**
 * Enumeration of states of the config parser automaton
 */
enum States{
    INITIAL, MACRO, OPERATOR, OPERATORMACRO, VALUE, BODY, INTVALUE, END
}